﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Logging;
using Microsoft.Practices.Prism;
using Castle.Windsor;
using Castle.MicroKernel.Registration;
using Tanmia.Basis.Properties;
using System.Globalization;
using Microsoft.Practices.Prism.Regions;
using Tanmia.Basis.ViewInterface;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.Prism.Events;
using CommonServiceLocator.WindsorAdapter;
using Microsoft.Practices.Prism.Regions.Behaviors;
using System.Security.Principal;
using Tanmia.Basis.Security;
using System.Windows;
using Tanmia.Basis.Concretes;
using Castle.MicroKernel;
using System.Threading;

namespace Tanmia.Basis.Prism
{
    /// <summary>
    /// Base class that provides a basic bootstrapping sequence that register most of the Composite Application library assets in a
    /// <see cref="IWindsorContainer"/>
    /// </summary>
    /// <remarks>This class must be overriden to provide application specific configuration.</remarks>
    public abstract class WindsorBootstrapper : Bootstrapper
    {
        private ISplashScreen splashScreen;
        private bool useDefaultConfiguration = true;

        /// <summary>
        /// Gets IRegionManager object for the application.
        /// </summary>
        protected IRegionManager RegionManagerObj { private set; get; }

        /// <summary>
        /// Gets the authenticated user who have logged in.
        /// </summary>
        public ApplicationUserInfo User { set; get; }

        public WindsorBootstrapper(ISplashScreen splashScreen)
        {
            if (splashScreen == null)
                throw new ArgumentNullException("splashScreen");

            this.splashScreen = splashScreen;
        }

        /// <summary>
        /// Gets the default <see cref="IWindsorContainer"/> for the application.
        /// </summary>
        /// <value>The default <see cref="IWindsorContainer"/> instance.</value>
        public IWindsorContainer Container { get; private set; }

        /// <summary>
        /// Configures the LocatorProvider for the <see cref="ServiceLocator"/>.
        /// </summary>
        protected override void ConfigureServiceLocator()
        {
            ServiceLocator.SetLocatorProvider(() => Container.Resolve<IServiceLocator>());
        }

        /// <summary>
        /// Run the bootstrapper process.
        /// </summary>
        /// <param name="runWithDefaultConfiguration">If <see langword="true"/>, registers default Composite Application Library services in the container. This is the default behavior.</param>
        public override void Run(bool runWithDefaultConfiguration)
        {
            this.useDefaultConfiguration = runWithDefaultConfiguration;

            if (useDefaultConfiguration)
                this.splashScreen.ShowSplash();

            this.splashScreen.UpdateSplashMessage(Resources.InitializingBootStrapper);

            this.Logger = CreateLogger();
            if (Logger == null)
                throw new InvalidOperationException(Resources.NullLoggerFacadeException);

            Logger.Log(Resources.LoggerCreatedSuccessfully, Category.Debug, Priority.Low);

            Logger.Log(Resources.CreatingWindsorContainer, Category.Debug, Priority.Low);
            this.Container = this.CreateContainer();
            if (Container == null)
                throw new InvalidOperationException(Resources.NullWindsorContainerException);

            Logger.Log(Resources.ConfiguringWindsorContainer, Category.Debug, Priority.Low);
            this.ConfigureContainer();

            Logger.Log(Resources.AuthenticatingUser, Category.Debug, Priority.Low);
            //if (useDefaultConfiguration)
                User = this.AuthenticateUser();
                Security.Adapter.SecurityAdapterInitializer.SetupPermission(Container);

            Logger.Log(Resources.CreatingModuleCatalog, Category.Debug, Priority.Low);
            this.ModuleCatalog = CreateModuleCatalog();
            if (ModuleCatalog == null)
                throw new InvalidOperationException(Resources.NullModuleCatalogException);

            Logger.Log(Resources.ConfiguringModuleCatalog, Category.Debug, Priority.Low);
            this.ConfigureModuleCatalog();

            Logger.Log(Resources.ConfiguringServiceLocatorSingleton, Category.Debug, Priority.Low);
            this.ConfigureServiceLocator();

            Logger.Log(Resources.ConfiguringRegionAdapters, Category.Debug, Priority.Low);
            this.ConfigureRegionAdapterMappings();

            Logger.Log(Resources.ConfiguringDefaultRegionBehaviors, Category.Debug, Priority.Low);
            this.ConfigureDefaultRegionBehaviors();

            Logger.Log(Resources.RegisteringFrameworkExceptionTypes, Category.Debug, Priority.Low);
            this.RegisterFrameworkExceptionTypes();

            Logger.Log(Resources.CreatingShell, Category.Debug, Priority.Low);
            this.Shell = CreateShell();
            if (Shell != null)
            {
                SetRegionManager();

                Logger.Log(Resources.UpdatingRegions, Category.Debug, Priority.Low);
                RegionManager.UpdateRegions();

                Logger.Log(Resources.InitializingShell, Category.Debug, Priority.Low);
                this.InitializeShell();
            }

            Logger.Log(Resources.InitializingModules, Category.Debug, Priority.Low);
            this.InitializeModules();

            Logger.Log(Resources.BootstrapperSequenceCompleted, Category.Debug, Priority.Low);

            this.splashScreen.UpdateSplashMessage(Resources.EndInitializingBootstrapper);

            if (useDefaultConfiguration)
                this.splashScreen.HideSplash();
        }

        protected virtual ApplicationUserInfo AuthenticateUser()
        {
            splashScreen.UpdateSplashMessage(Resources.SplashUserAuthentication);

            IUserSelector userSelector = Container.Resolve<IUserSelector>();
            IAuthenticationService authenticationService = Container.Resolve<IAuthenticationService>();
            ApplicationUserInfo selectedUser = null;

            do
            {
                selectedUser = userSelector.SelectUser();
            }
            while (!authenticationService.Authenticate(selectedUser));

            return authenticationService.User;
        }

        /// <summary>
        /// Set RegionManager to the Shell Window by default.
        /// </summary>
        protected virtual void SetRegionManager()
        {
            Logger.Log(Resources.SettingTheRegionManager, Category.Debug, Priority.Low);
            RegionManagerObj = Container.Resolve<IRegionManager>();
            RegionManager.SetRegionManager(Shell, RegionManagerObj);
        }

        /// <summary>
        /// Configures the <see cref="IWindsorContainer"/>. May be overriden in a derived class to add specific
        /// type mappings required by the application.
        /// </summary>
        protected virtual void ConfigureContainer()
        {
            Container.Register(Component.For<ILoggerFacade>().Instance(Logger));

            Container.Register(Component.For<ISplashScreen>().Instance(splashScreen));

            Container.Register(Component.For<IWindsorContainer>().Instance(Container));
            RegisterTypeIfMissing(typeof(IServiceLocator), typeof(WindsorServiceLocator), true);
            RegisterTypeIfMissing(typeof(IModuleInitializer), typeof(WindsorModuleInitializer), true);
            RegisterTypeIfMissing(typeof(IModuleManager), typeof(ModuleManager), true);
            RegisterTypeIfMissing(typeof(RegionAdapterMappings), typeof(RegionAdapterMappings), true);
            RegisterTypeIfMissing(typeof(IRegionManager), typeof(RegionManager), true);
            RegisterTypeIfMissing(typeof(IEventAggregator), typeof(EventAggregator), true);
            RegisterTypeIfMissing(typeof(IRegionViewRegistry), typeof(RegionViewRegistry), true);
            RegisterTypeIfMissing(typeof(IRegionBehaviorFactory), typeof(RegionBehaviorFactory), true);
            RegisterTypeIfMissing(typeof(IRegionNavigationJournalEntry), typeof(RegionNavigationJournalEntry), false);
            RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournal), false);
            RegisterTypeIfMissing(typeof(IRegionNavigationService), typeof(RegionNavigationService), false);
            RegisterTypeIfMissing(typeof(IRegionNavigationContentLoader), typeof(ModifiedRegionNavigationContentLoader), true);
            RegisterTypeIfMissing(typeof(DelayedRegionCreationBehavior), typeof(DelayedRegionCreationBehavior), false);
            RegisterTypeIfMissing(typeof(WindowRegionAdapter), typeof(WindowRegionAdapter), true);

            // Register region adapter
            Container.Register(AllTypes.FromAssemblyContaining<IRegionAdapter>().BasedOn<IRegionAdapter>()
                .Configure(c => c.LifeStyle.Transient));

            // Register region behaviors
            Container.Register(AllTypes.FromAssemblyContaining<IRegionBehavior>().BasedOn<IRegionBehavior>()
                .Configure(c => c.LifeStyle.Transient));
        }

        /// <summary>
        /// Registers a type in the container only if that type was not already registed.
        /// </summary>
        /// <param name="fromType">The interface type to register.</param>
        /// <param name="toType">The type implementing the interface.</param>
        /// <param name="registerAsSingelton">Registers the type as a singelton.</param>
        protected void RegisterTypeIfMissing(Type fromType, Type toType, bool registerAsSingelton)
        {
            if (fromType == null)
                throw new ArgumentNullException("fromType");

            if (toType == null)
                throw new ArgumentNullException("toType");

            if (Container.Kernel.HasComponent(fromType))
                Logger.Log(string.Format(CultureInfo.CurrentCulture, Resources.TypeMappingAlreadyRegistered, fromType.Name), Category.Debug, Priority.Low);
            else
                if (registerAsSingelton)
                    Container.Register(Component.For(fromType).ImplementedBy(toType).LifeStyle.Singleton);
                else
                    Container.Register(Component.For(fromType).ImplementedBy(toType).LifeStyle.Transient);
        }

        /// <summary>
        /// Creates the <see cref="IWindsorContainer"/> that will be used as the default container.
        /// </summary>
        /// <returns>A new instance of <see cref="IWindsorContainer"/>.</returns>
        protected virtual IWindsorContainer CreateContainer()
        {
            return new WindsorContainer();
        }

        /// <summary>
        /// Registers in the <see cref="IWindsorContainer"/> the <see cref="Type"/> of the exceptions that are not considered root exceptions
        /// by the <see cref="ExceptionExtensions"/>
        /// </summary>
        protected override void RegisterFrameworkExceptionTypes()
        {
            base.RegisterFrameworkExceptionTypes();

            ExceptionExtensions.RegisterFrameworkExceptionType(typeof(Castle.Windsor.InitializationException));
            ExceptionExtensions.RegisterFrameworkExceptionType(typeof(Castle.MicroKernel.ComponentNotFoundException));
        }

        /// <summary>
        /// Initializes the modules. May be overriden in a derived class to use a custom Modules Catalog
        /// </summary>
        protected override void InitializeModules()
        {
            IModuleManager manager;

            try
            {
                manager = Container.Resolve<IModuleManager>();
            }
            catch (ComponentNotFoundException ex)
            {
                if (ex.Message.Contains("IModuleCatalog"))
                    throw new InvalidOperationException(Resources.NullModuleCatalogException);
                throw;
            }

            manager.Run();
        }

        /// <summary>
        /// Create the <see cref="Log4NetLogger"/> that implements <see cref="ILoggerFacade"/> which used by the bootstrapper.
        /// </summary>
        /// <returns></returns>
        protected override ILoggerFacade CreateLogger()
        {
            base.CreateLogger();
            return new Log4NetLogger();
        }

        /// <summary>
        /// Creates the Microsoft.Practices.Prism.Modularity.IModuleCatalog used by Prism.
        /// </summary>
        /// <returns>The base implementation returns a new ModuleCatalog.</returns>
        protected override IModuleCatalog CreateModuleCatalog()
        {
            IAuthorizationService authSrv = Container.Resolve<IAuthorizationService>();
            IModuleCatalog moduleCatalog = new AuthorizedDirectoryModuleCatalog(Container, authSrv) { ModulePath = "..\\..\\blocks" };

            Container.Register(Component.For<IModuleCatalog>().Instance(moduleCatalog));

            return moduleCatalog;
        }

        protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
        {
            RegionAdapterMappings regionAdapterMappings = Container.Resolve<RegionAdapterMappings>();
            WindowRegionAdapter windowRegionAdapter = Container.Resolve<WindowRegionAdapter>();
            windowRegionAdapter.WindowStyle = Application.Current.FindResource("WindowStyle") as Style;
            regionAdapterMappings.RegisterMapping(typeof(Window), windowRegionAdapter);

            return base.ConfigureRegionAdapterMappings();
        }

        protected virtual void RegisterNamedType(Type componentType, bool registerAsSingelton)
        {
            if (componentType == null)
                throw new ArgumentNullException("componentType");

            if (registerAsSingelton)
                Container.Register(Component.For(componentType).Named(componentType.Name).LifeStyle.Singleton);
            else
                Container.Register(Component.For(componentType).Named(componentType.Name).LifeStyle.Transient);
        }
    }
}
